package com.juitar.chordfinder;

import java.util.Arrays;
import java.util.Comparator;
import java.util.HashSet;
import java.util.StringTokenizer;

import com.juitar.base.*;

public class Neck {
    
    public int [][] neck;
    Tuning tuning;
    
    public Neck() {
        neck = new int [Tuning.STANDARD.strings()] [12];
        this.tuning = Tuning.STANDARD;
    }
    
    public Neck(Tuning tuning) {
        neck = new int [tuning.strings()] [12];
        this.tuning = tuning;
    }
    
    
    /**
     * Method findChord.
     * @param chord
     * @param root
     */
    public void fillNeck(Chord chord) {
        int [] openStringNotes = tuning.getOpenStringNotes();
        //int [] chordNotes = c.getNotes();
        for (int i=0; i<tuning.strings(); i++) {
            int note = openStringNotes[i];
            for (int j=0; j<12; j++) {
                neck[i][j] = chord.contains(note + j);
            }
        }
        findChords(chord);
    }
    
    public boolean isChord(int [] app, Chord chord) {
        int [] openStringNotes = tuning.getOpenStringNotes();
        int [] flags = new int [chord.numOfNotes()];
        
        for (int i=0; i<app.length; i++) {
            int n = chord.contains(openStringNotes[i]+app[i]);
            if (n != 0) {
                flags[n-1] = 1;
            } else {
                app[i] = -1;
            }
        }
        for (int i=0; i<flags.length; i++) {
            if (flags[i] == 0) {
                return false;
            }
        }
        return true;
    }
    
    public void printAllChords(java.io.Writer writer, Chord chord) throws java.io.IOException {
        int [] app = new int [neck.length];
        do {
            if (isChord(app, chord)) {
                printChord(writer, app);
            }
        } while (nextApp(app));
    }
    
    public boolean nextApp(int [] app) {
        for (int i=0; i<app.length-1; i++) {
            app[i] += 1;
            if (app[i] < 12) {
                return true;
            } else {
                app[i]=0;
                app[i+1] += 1;
            }
        }
        if (app[app.length-1] < 12) {
            return true;
        } else {
            return false;
        }
    }
/*
    app = {5,4,3,-1,0,3};
 
       3     4     5
    |-----|-----|--o--|
    |-----|--o--|-----|
    |--o--|-----|-----|
   x|-----|-----|-----|
   o|-----|-----|-----|
    |--o--|-----|-----|
 */
    public void printChord(java.io.Writer writer, int[] app) throws java.io.IOException {
        int max = 0;
        int min = Integer.MAX_VALUE;
        for (int i=0; i<app.length; i++) {
            if (app[i] <= 0) continue;
            if (app[i] < min) min = app[i];
            if (app[i] > max) max = app[i];
        }
        
        if ((max-min) > 5) return;
        
        for (int j=min; j<max+1; j++) {
            writer.write("    ");
            if (j>min) writer.write(" ");
            writer.write(""+j);
        }
        writer.write("\n");
        
        for (int i=0; i<app.length; i++) {
            if (app[i] == 0) {
                writer.write("o");
            } else if (app[i] == -1) {
                writer.write("x");
            } else {
                writer.write(" ");
            }
            for (int j=min; j<max+1; j++) {
                if (app[i]==j) {
                    writer.write("|--o--");
                } else {
                    writer.write("|-----");
                }
            }
            writer.write("|\n");
        }
    }
    
    public void printHTML(java.io.Writer writer) throws java.io.IOException {
        writer.write("<pre>\n");
        for (int i =0; i<tuning.strings(); i++) {
            for (int j=0; j<12; j++) {
                int intNote = tuning.getOpenStringNotes()[i] + j;
                String strNote = Note.toString(intNote);
                String trailer = null;
                int strNoteLength = strNote.length();
                if (strNoteLength==1) {
                    trailer = "--|";
                } else {
                    trailer = "-|";
                }
                
                int n = neck[i][j];
                if (n==1) { //Root note of the chord
                    strNote = "<b>" + strNote + "</b>";
                }
                if (j==0) { //First fret
                    if (n > 0){
                        if (strNoteLength == 1) {
                            writer.write((i+1)+"||"+strNote);
                        } else {
                            writer.write((i+1)+"|"+strNote);
                        }
                    } else {
                        writer.write((i+1)+"|||");
                    }
                } else {
                    if (n > 0) {
                        writer.write("--"+strNote+trailer);
                    } else {
                        writer.write("-----|");
                    }
                }
            }
            writer.write("\n");
        }
        writer.write("</pre>");
    }
    
	public void findChords(Chord chord) {

		int strings = tuning.strings();
		int chords = 1;
		for (int i = 0; i < strings; i++) {
			chords *= 5;
		}

		HashSet set = new HashSet();
		int[] app = new int[strings];
		for (int i = 0; i < 8; i++) {
			for (int j = 0; j < chords; j++) {
				int ch = j;
				boolean[] noteFlags = new boolean[chord.numOfNotes()];
				for (int k = 0; k < strings; k++) {
					int shift = ch % 5;
					if (shift == 0) {
						app[k] = 0;
					} else {
						app[k] = i + shift - 1;
					}
					if (neck[k][app[k]] == 0) {
						app[k] = -1;
					} else {
						noteFlags[neck[k][app[k]] - 1] = true;
					}
					ch /= 5;
				}
				//Check if chord consists of all required notes
				boolean ok = true;
				for (int n = 0; n < noteFlags.length; n++) {
					if (noteFlags[n] == false) {
						ok = false;
						break;
					}
				}
				//Print found chord
				if (ok) {
					String s = new String();
					for (int n = app.length-1; n >=0 ; n--) {
						if (app[n] == -1) {
							s += "x ";
						} else {
							s += app[n]+" ";
						}
					}
					//boolean added = set.add(s.trim());
					//if (added) {
					//	System.out.println(s);
					//}
				}
			}
		}
		Object [] arr = set.toArray();
		Arrays.sort(arr, new FingeringComparator());
		for (int i = 0; i < arr.length; i++) {
			System.out.println(arr[i]);
		}
	}
    
    public static int [] fretSpacing(int fretLength) {
        int [] spacing = new int [13];
        double currentFactor = 1.0;   //factor;
        
        for (int i=0; i<spacing.length; i++) {
            spacing[i] = (int) (2*fretLength*(1-1/currentFactor));
            currentFactor *= factor;
        }
        return spacing;
    }
    
    /**Fret length factor*/
    static private double factor  = java.lang.Math.pow(2,1.0/12.0);
}

class FingeringComparator implements Comparator {
	/**
	 * @see java.util.Comparator#compare(Object, Object)
	 */
	public int compare(Object o1, Object o2) {
		if (o1 == o2) {
			return 0;
		} else {
			String s1 = (String) o1;
			String s2 = (String) o2;
			return metrics(s1) - metrics(s2);
		}
	}
	
	int metrics(String s) {
		int m = 0;
		StringTokenizer t = new StringTokenizer(s);
		while(t.hasMoreTokens()) {
			String x = t.nextToken();
			if (x.compareTo("x")==0) {
				m += 144;
			} else {
				m += Integer.parseInt(x);
			}
		}
		return m;
	}
}
